#!/usr/bin/env python
# -*- coding: utf-8 -*-
# 时间     : 2020/06/11 20:48
# 作者     : DL (584054432@qq.com)
# 网址     : https://gitee.com/dingliangPython/
# 软件版本 : Python3.7.3
# 功能     ：
import os
import re
import sys

import connect_database
import loadLog
import timeEXT
import time_ext
from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QThread
from PyQt5.QtWidgets import QApplication, QDialog
from PyQt5.QtWidgets import QFileDialog, QMessageBox, QTableView, QTableWidgetItem
from pyqt5_common import APP_CONFIG_PATH, APP_NAME, show_message
from sqlalchemy.orm import Session

import s7config_window
import sdk_config_window
from config_ui import Ui_Form
import gcl_sdk
import logging
import s7

import sqlalchemy_client
# import local_config
from local_config import *


class S7Thread(QThread):
    """docstring for S7Thread"""
    read_config_signal = pyqtSignal(list)
    start_signal = pyqtSignal(bool)
    start_flag = None
    clinet = None

    def __init__(self, config, s7_read_signal = None, flag = None):
        super(S7Thread, self).__init__()
        self.config = config
        self.s7_read_signal = s7_read_signal
        self.start_flag = flag
        self.read_config = []
        self.name = self.config[0]
        self.log = logging.getLogger("main.plc_thread")

        self.client = s7.S7Client()
        self.read_config_signal.connect(self.set_read_config)
        self.start_signal.connect(self.set_start_flag)

    # 设置启停标志
    def set_start_flag(self, value):
        self.start_flag = value
        if value and not self.isRunning():
            self.start()
        if not value:
            if self.client and self.client.get_connected():
                self.client.destroy()

    def set_read_config(self, config):
        self.read_config = config

    def connect(self):
        if self.client and self.client.get_connected():
            return
        try:
            (name, host, port, rack, slot, tsap) = self.config
            if rack and slot:
                self.client.connect(host, rack = int(rack), slot = int(slot), tcpport = int(port))
            else:
                tsap = tsap.replace('.', '')
                self.client.set_connection_params(host, local_tsap = 0x1010, remote_tsap = int(tsap, 16))
                self.client.connect(host, rack = 0, slot = 0, tcpport = int(port))

        except Exception as e:
            info = "PLC客户端:{},连接失败,异常:{}".format(self.name, e)
            self.log.error(info)
            # show_message(info)

    def run(self):

        if not self.start_flag:
            self.log.warning("停止休眠PLC连接:{}".format(self.name))
            return

        self.connect()
        if self.client.get_connected():
            self.log.info("PLC客户端:{},连接PLC成功....".format(self.name))
            self.run_s7_task()
        # else:
        #     self.log.info("PLC客户端:{},没有在运行...".format(self.name))

    # 运行s7任务
    def run_s7_task(self):
        try:
            while True:
                QThread.sleep(1)
                if not self.client.get_connected():
                    self.check_connect()
                    QThread.sleep(5)
                    continue

                if not self.read_config:
                    # self.log.warning("地址列错误或为空，没法读取")
                    QThread.sleep(1)
                    continue

                value_dice = {}
                for row in self.read_config:
                    data = self.client.read_value(row)
                    if data is None:
                        continue
                    value_dice.update({row.get('addr'): data})
                self.s7_read_signal.emit({self.name: value_dice})


        except Exception as e:
            self.log.info("解析或读出异常：{}".format(e))

    # 读取数据
    def read(self, config):
        param = self.client.get_read_param(config)
        data = self.client.read_area(*param[:4])
        value = self.client.parse_data(data, param)
        return value

    # 写数据
    def write(self, config, value):
        return self.client.write_data(config, value)

    # 检测连接
    def check_connect(self, e = "连接失败"):
        status = None
        for i in range(30):
            status = self.client.get_connected()
            if not status:
                self.sleep(1)
                self.log.warning("第{}次,检测PLC客户端:{},连接:{},失败".format(i + 1, self.name, status))
            else:
                status = True
                break
        if status:
            self.log.warning("重新检测PLC连接成功,启动读取线程")
            self.start()
        else:
            self.log.warning("PLC客户端:{},检测连接30秒,连接依然失败:{},检测PLC连接,10秒后退出程序....".format(self.name, e))
            self.sleep(10)
            os._exit(0)


class Window(QDialog):
    """docstring for Window"""
    app_name_signal = pyqtSignal(str)
    s7_rt_value_signal = pyqtSignal(dict)

    password_enable = None
    sdk = None
    s7_clinet_dict = {}

    def __init__(self, log_callback = None):
        super(Window, self).__init__()
        self.ui = Ui_Form()
        self.ui.setupUi(self)
        self.loacl_connect = self.get_local_connect()
        # 日志显示
        self.log_config = self.get_log_config()
        self.log = loadLog.LoadLog(self.log_config, 'main', callback = log_callback)

        self.s7_window = s7config_window.Window()
        self.sdk_config_window = sdk_config_window.Window(self.ui.start_sdk.isChecked())

        # 不能编辑
        self.ui.connect_list.setEditTriggers(QTableView.NoEditTriggers)

        self.load_local_config()
        self.connectList = self.get_conn_list()

        self.load_s7_connect()

    def load_s7_connect(self):
        if not self.s7_window.config:
            return
        for key, values in self.s7_window.config.items():
            th = S7Thread(values, self.s7_rt_value_signal, self.ui.start_s7.isChecked())
            th.start()
            self.s7_clinet_dict[key] = th

    # 获取本地配置数据库
    def get_local_connect(self):
        config_path = os.path.join(APP_CONFIG_PATH, APP_NAME, 'config.db')

        if not os.path.isfile(config_path):
            logging.warning(" * 没有找到配置文件：{}，初始化一个配置文件\n".format(config_path))

        config = {'databasepath': config_path, 'type': 'sqlite'}
        local = sqlalchemy_client.Connect(config)
        # 初始化配置
        init_config(local)

        return local

    def closeEvent(self, event):
        if show_message("配置改变重启程序才能生效，是否退出程序"):
            os._exit(0)
        else:
            self.hide()

    # 显示S7配置窗口
    @pyqtSlot(bool)
    def on_show_s7_config_clicked(self):
        self.s7_window.hide()
        self.s7_window.show()

    # 显示sdk配置窗口
    @pyqtSlot(bool)
    def on_show_sdk_config_clicked(self):
        self.sdk_config_window.hide()
        self.sdk_config_window.show()

    # 设置端口
    def on_type_currentTextChanged(self, _type):
        if _type == "Oracle":
            self.ui.port.setValue(1521)
        elif _type == "SQLServer":
            self.ui.port.setValue(1433)
        elif _type == "Mysql":
            self.ui.port.setValue(3306)
        elif _type == "Postgresql":
            self.ui.port.setValue(5432)
        elif _type == "Sqlite":
            self.ui.port.clear()
            self.ui.password.clear()
            self.ui.username.clear()
            self.ui.database.clear()
            self.ui.dsn.clear()
        elif _type == "Access":
            self.ui.host.clear()
            self.ui.port.clear()
            self.ui.database.clear()
            self.ui.dsn.clear()

    # 获取连接数据显示
    def get_conn_list(self):
        self.ui.connect_list.clear()
        with self.loacl_connect.get_session() as session:
            session.autocommit = True
            data = session.query(connect_table)
            values = data.all()
            if not values: return
            config = [{k['name']: row[i] for i, k in enumerate(data.column_descriptions)} for row in values]
            if not data:
                self.log.warning("请先增加数据库连接，没有查询到已配置的数据库")
                self.ui.connect_list.clear()
                return
            self.ui.connect_list.addItems([row['name'] for row in config])
            self.ui.connect_list.itemClicked.connect(self.show_connect_info)

    # 显示连接信息
    def show_connect_info(self, item):
        value = item.text()
        with self.loacl_connect.get_session() as session:
            data = session.query(connect_table).filter_by(name = value)
            value = data.first()
            config = {k['name']: value[i] for i, k in enumerate(data.column_descriptions)}
            self.ui.name.setText(config.get('name') or '')

            item_text = [self.ui.mode.itemText(i) for i in range(self.ui.mode.count())]
            _mode = str(config.get('mode')) if config.get('mode') else ''
            if _mode and _mode in item_text:
                self.ui.mode.setCurrentText(_mode)
            else:
                self.ui.mode.addItem(_mode)
                self.ui.mode.setCurrentText(_mode)

            item_text = [self.ui.type.itemText(i) for i in range(self.ui.type.count())]
            _type = config.get('type')
            if _type and _type in item_text:
                self.ui.type.setCurrentText(config.get('type'))
            else:
                self.ui.type.addItem(_type)
                self.ui.type.setCurrentText(_type)
            self.ui.username.setText(config.get('username', ''))
            self.ui.password.setText(config.get('password', ''))
            self.ui.database.setText(config.get('database', ''))
            self.ui.host.setText(config.get('host', ''))
            self.ui.port.setValue(config.get('port') or 0)
            self.ui.mincached.setValue(config.get('mincached') or 0)
            self.ui.maxconnections.setValue(config.get('maxconnections') or 0)
            self.ui.dsn.setText(config.get('dsn') or '')
            self.ui.databasepath.setText(config.get('databasepath'))
            self.ui.left.setChecked(config.get('left') or False)
            self.ui.right.setChecked(config.get('right') or False)
            self.ui.format.setText(config.get('format'))

    # 存储更新数据库
    @pyqtSlot(bool)
    def on_save_connnect_clicked(self):
        with self.loacl_connect.get_session() as session:
            session.autocommit = True
            name = self.ui.name.text()
            if not name:
                show_message("请选择设置连接名称。。。")
                return
            # 获取值
            _name = self.ui.name.text()
            _type = self.ui.type.currentText()
            _mode = self.ui.mode.currentText()
            _host = self.ui.host.text()
            _port = self.ui.port.value()
            _username = self.ui.username.text()
            _password = self.ui.password.text()
            _database = self.ui.database.text()
            _dsn = self.ui.dsn.text()
            _mincached = self.ui.mincached.value()
            _maxconnections = self.ui.maxconnections.value()
            _databasepath = self.ui.databasepath.text()
            _format = self.ui.format.text()
            _left = 2 if self.ui.left.isChecked() else 0
            _right = 2 if self.ui.right.isChecked() else 0
            # 查询数据
            obj = session.query(connect_table).filter_by(name = name)
            # 更新值
            values = {
                    'type': _type, 'mode': _mode, 'host': _host, 'port': _port, 'username': _username,
                    'password': _password, 'database': _database, 'dsn': _dsn,
                    'mincached': _mincached, 'maxconnections': _maxconnections, 'databasepath': _databasepath,
                    'format': _format, 'left': _left, 'right': _right
                    }
            # 更新
            if obj.first():
                result = obj.update(values)
                if not result: return
                self.log.info("修改连接：{}，成功。。。".format(name))
            # 插入
            else:
                values['name'] = _name
                ins = connect_table.insert().values(values)
                result = session.execute(ins).rowcount
                if not result: return
                self.log.info("增加连接：{}，成功。。。".format(name))
        # 重新加载
        self.get_conn_list()

    # 删除连接
    @pyqtSlot(bool)
    def on_del_connect_clicked(self):
        with self.loacl_connect.get_session() as session:
            session.autocommit = True
            # 获取值
            _name = self.ui.name.text()
            if show_message("确认需要删除：{} 的连接配置嘛？".format(_name)):
                result = session.query(connect_table).filter_by(name = _name).delete()
                if not result:
                    self.log.warning("删除连接：{},失败，请确认有此连接".format(_name))
                    return
                self.log.warning("删除连接：{},成功".format(_name))
        # 重新加载
        self.get_conn_list()

    # 获取文件路径
    def get_file_path(self):
        left = self.ui.left.isChecked()
        timeFormat = self.ui.format.text()
        filePath = self.ui.databasepath.text()
        if timeFormat and filePath:
            fileDir, name = os.path.split(filePath)
            fileName, ext = os.path.splitext(name)
            # 替换库名前后有可能的数字
            fileName = re.sub('^\d+', "", fileName) if left else re.sub('\d+$', "", fileName)
            # 根据格式获取时间值
            extValue = timeEXT.FormatStr(timeEXT.GetUtc(), formatStr = timeFormat)
            newName = extValue + fileName + ext if left else fileName + extValue + ext
            newPath = os.path.normpath(os.path.join(fileDir, newName))
            return newPath
        return filePath

    # 设置显示app配置
    def load_local_config(self):
        with self.loacl_connect.get_session() as session:
            session.autocommit = True
            _id, name, sdk_start, s7_start, password_start = session.query(config_table).first()
            self.ui.app_id.setText(str(_id) or '')
            self.ui.app_name.setText(name or '')
            self.ui.start_sdk.setChecked(sdk_start or 0)
            self.ui.start_s7.setChecked(s7_start or 0)
            self.ui.start_password.setChecked(password_start or 0)
            self.password_enable = password_start

            self.ui.app_id.textChanged.connect(lambda v: self.set_app_config("AppId", v))
            self.ui.app_name.textChanged.connect(lambda v: self.set_app_config("AppName", v))
            self.ui.start_sdk.stateChanged.connect(lambda v: self.set_app_config("StartSdk", v))
            self.ui.start_s7.stateChanged.connect(lambda v: self.set_app_config("StartS7", v))
            self.ui.start_password.stateChanged.connect(lambda v: self.set_app_config("EnablePassword", v))

    # 设置app
    def set_app_config(self, field, value):
        with self.loacl_connect.get_session() as session:
            session.autocommit = True
            session.query(config_table).update({field: value})

            if field == "StartSdk":
                config_path = os.path.join(APP_CONFIG_PATH, APP_NAME)
                if value:
                    self.sdk = gcl_sdk.GCL_SDK(config_path = config_path)
                    self.log.info("启动sdk")
                    self.sdk_config_window.sdk_start_signal.emit(True)
                    return

                if self.sdk and show_message("关闭SDK,需要关闭程序,是否退出程序"):
                    self.sdk.uninit()
                    self.sdk = None
                    self.log.info("停止sdk")

            if field == "EnablePassword":
                self.password_enable = value
                self.log.info('启用或停用密码,状态:{}'.format('启用' if value else '停用'))

            if field == "StartS7":
                if value and os.path.exists('./snap7.dll'):

                    self.log.info("启动S7")
                    for key, thread in self.s7_clinet_dict.items():
                        thread.start_signal.emit(True)
                else:
                    self.log.info("停止S7")
                    for key, thread in self.s7_clinet_dict.items():
                        thread.start_signal.emit(False)

    # 初始流水序号
    @pyqtSlot(bool)
    def on_init_count_clicked(self):
        if not show_message("请确认是否清除存储流水序号记录，清除后，流水从1开始。。。"): return
        with self.loacl_connect.get_session() as session:
            session.autocommit = True
            result = session.query(flag_table).delete()
            if result:
                show_message("清除流水序号成功")

    # 设置显示日志
    def get_log_config(self):
        with self.loacl_connect.get_session() as session:
            data = session.query(log_table)
            value = data.first()
            config = {k['name']: value[i] for i, k in enumerate(data.column_descriptions)}

            level, save, path, cycle, unit, number, size = value
            self.ui.log_level.setCurrentText(str(level))
            self.ui.log_save.setChecked(save)
            self.ui.log_path.setText(path)
            self.ui.log_number.setValue(number)
            self.ui.log_cycle.setValue(cycle)
            unit = {'D天': 0, 'H小时': 1, 'M分钟': 2, 'S秒钟': 3, 'D': 0, 'H': 1, 'M': 2, 'S': 3}.get(unit)
            self.ui.log_unit.setCurrentIndex(unit)
            self.ui.log_size.setValue(size)

            self.ui.log_level.currentTextChanged.connect(lambda v: self.set_log_config("LogLevel", v))
            self.ui.log_save.stateChanged.connect(lambda v: self.set_log_config("LogSave", v))
            self.ui.log_path.textChanged.connect(lambda v: self.set_log_config("LogSavePath", v))
            self.ui.log_number.valueChanged.connect(lambda v: self.set_log_config("LogSaveNumber", v))
            self.ui.log_cycle.valueChanged.connect(lambda v: self.set_log_config("LogSaveCycle", v))
            self.ui.log_unit.currentTextChanged.connect(lambda v: self.set_log_config("LogSaveUnit", v))
            self.ui.log_size.valueChanged.connect(lambda v: self.set_log_config("LogSaveSize", v))
            self.ui.open_log_path.clicked.connect(
                    lambda: self.ui.log_path.setText(QFileDialog.getExistingDirectory(None, "日志目录", './')))
            return config

    # 日志设置
    def set_log_config(self, field, value):
        with self.loacl_connect.get_session() as session:
            session.autocommit = True
            session.query(log_table).update({field: value})

            if field == "LogSave":
                if show_message("{}，日志文件保存，需要重启程序生效。。。".format("启用" if value else "停用")):
                    os._exit(0)

            if field != "LogLevel": return
            self.log.info("设置日志等级= {}".format(value))
            for handler in self.log.logger.handlers:
                handler.setLevel(int(value))

    # 打开库路径
    @pyqtSlot(bool)
    def on_open_file_path_clicked(self):
        self.ui.databasepath.setText(
                QFileDialog.getOpenFileName(None, '打开数据库文件', './', 'files (*.mdb *.db *.accdb)')[0])


if __name__ == '__main__':
    # import localDB

    # log = loadLog.Logger(name = 'main')
    config = {'databasepath': './app/config.db', 'type': 'sqlite'}
    conn = connect_database.Connect(**config)
    app = QApplication(sys.argv)
    gui = Window()
    gui.show()
    sys.exit(app.exec_())